#ifdef __cplusplus
extern "C" {
#endif

#include <string.h> /* memcpy(), memset() */
#include <stdio.h>  // sprintf()
#include <stdlib.h> /* malloc() */

#ifdef __cplusplus
}
#endif
#include "physmem.hh" /* class PHYSMEM_HANDLE */
#include "rcs_print.hh"
PHYSMEM_HANDLE::PHYSMEM_HANDLE()
{
    size = 0;
    offset = 0;
    temp_buf = NULL;
    local_address = (LOCAL_ADDRESS_TYPE)NULL;
    physical_address = 0;
    using_bit3 = 0;
    isvalid = 1;
    total_bytes_moved = 0;
    enable_byte_counting = 0;
}

PHYSMEM_HANDLE::PHYSMEM_HANDLE(unsigned long _physical_address, long _address_code, long _size)
{
    temp_buf = NULL;
    physical_address = _physical_address;
    size = _size;
    address_code = _address_code;
    local_address = (LOCAL_ADDRESS_TYPE)NULL;
    isvalid = 1;
    offset = 0;
    using_bit3 = 0;

    if (0 == physical_address)
    {
        local_address = (LOCAL_ADDRESS_TYPE)NULL;
        return;
    }
    local_address = (LOCAL_ADDRESS_TYPE)physical_address;
}

/* Destructor. */
PHYSMEM_HANDLE::~PHYSMEM_HANDLE() {}

/* Use an ordinary pointer to access memory. */
void PHYSMEM_HANDLE::set_to_ptr(void* _ptr, long _size)
{
    local_address = (LOCAL_ADDRESS_TYPE)_ptr;
    size = _size;
    offset = 0;
}

static int physmem_read_local_address_is_null_error_print_count = 0;

/***********************************************************
* Read _read_size bytes from physical memory to store at _to.
* Returns: 0 for success or -1 for failure.
**********************************************************/
int PHYSMEM_HANDLE::read(void* _to, long _read_size)
{
    if (NULL == _to)
    {
        rcs_print_error("PHYSMEM_HANDLE::read _to = NULL.\n");
        return (-1);
    }
    /* Check sizes. */
    if (_read_size + offset > size || offset < 0)
    {
        rcs_print_error("PHYSMEM_HANDLE: Can't read %ld bytes at offset %ld from buffer of size %ld.\n", _read_size, offset, size);
        return (-1);
    }

    if (enable_byte_counting)
    {
        total_bytes_moved += _read_size;
    }

    /* If local_address has been initialized use it as an ordinary pointer. */
    if (NULL != local_address)
    {
        char* from;
        from = ((char*)local_address) + offset;
        if (_read_size == 2)
        {
            short* sfrom = (short*)from;
            short sval;
            sval = *sfrom;
            short* sto = (short*)_to;
            *sto = sval;
        }
        else
        {
            memcpy(_to, from, _read_size);
        }
        return (0);
    }

    /* include platform specific ways of accessing phsical memory here. */
    if (!(physmem_read_local_address_is_null_error_print_count % 100000))
    {
        rcs_print_error("PHYSMEM_HANDLE: Cannot read from physical memory when local address is NULL.\n");
        rcs_print_error("(This error has occured %d times.)\n", physmem_read_local_address_is_null_error_print_count + 1);
    }
    physmem_read_local_address_is_null_error_print_count++;
    return (-1);
}

static int physmem_write_local_address_is_null_error_print_count = 0;

/***********************************************************
* Write _write_size bytes from memory at _from to physical memory.
* Returns: 0 for success or -1 for failure.
**********************************************************/
int PHYSMEM_HANDLE::write(void* _from, long _write_size)
{
    if (NULL == _from)
    {
        rcs_print_error("PHYSMEM_HANDLE:write _from = NULL\n");
        return -1;
    }

    /* Check sizes. */
    if (_write_size + offset > size || offset < 0)
    {
        rcs_print_error("PHYSMEM_HANDLE: Can't write %ld bytes at offset %ld from buffer of size %ld.\n", _write_size, offset, size);
        return (-1);
    }
    if (enable_byte_counting)
    {
        total_bytes_moved += _write_size;
    }
    /* If local_address has been initialized use it as an ordinary pointer. */
    if (NULL != local_address)
    {
        char* to;
        to = ((char*)local_address) + offset;
        if (_write_size == 2)
        {
            short* sto = (short*)to;
            short sval = *(short*)_from;
            *sto = sval;
        }
        else
        {
            memcpy(to, _from, _write_size);
        }
        return (0);
    }

    /* include platform specific ways of accessing phsical memory here. */
    if (!(physmem_write_local_address_is_null_error_print_count % 100000))
    {
        rcs_print_error("PHYSMEM_HANDLE: Cannot write to physical memory when local address is NULL.\n");
        rcs_print_error("(This error has occured %d times.)\n", physmem_write_local_address_is_null_error_print_count + 1);
    }
    physmem_write_local_address_is_null_error_print_count++;
    return (-1);
}

void PHYSMEM_HANDLE::memsetf(long _memset_offset, char _byte, long _memset_size)
{
    /* Check sizes. */
    if (_memset_size + _memset_offset > size)
    {
        return;
    }

    /* If local_address has been initialized use it as an ordinary pointer. */
    if (NULL != local_address)
    {
        char* temp_addr;
        temp_addr = ((char*)local_address) + _memset_offset;
        memset(temp_addr, _byte, _memset_size);
        return;
    }
    else
    {
        /* Since local address is not initialized use temp_buf and write to
	   access the physical memory in an platform specific way. */
        if (NULL == temp_buf)
        {
            temp_buf = (char*)malloc(size);
        }
        if (NULL != temp_buf)
        {
            if (_memset_size + _memset_offset <= size)
            {
                memset(temp_buf, _byte, _memset_size);
                unsigned long old_offset;
                old_offset = offset;
                offset = _memset_offset;
                write(temp_buf, _memset_size);
                offset = old_offset;
            }
            else
            {
                memset(temp_buf, _byte, size - _memset_offset);
                unsigned long old_offset;
                old_offset = offset;
                offset = _memset_offset;
                write(temp_buf, size - offset);
                offset = old_offset;
            }
        }
    }
}

int PHYSMEM_HANDLE::clear_memory()
{
    /* If local_address has been initialized use it as an ordinary pointer. */
    if (NULL != local_address)
    {
        memset(local_address, 0, size);
        return (0);
    }
    else
    {
        /* Since local address is not initialized use temp_buf and write to
	   access the physical memory in an platform specific way. */
        if (NULL == temp_buf)
        {
            temp_buf = (char*)malloc(size);
        }
        if (NULL == temp_buf)
        {
            return (-1);
        }
        memset(temp_buf, 0, size);
        unsigned long old_offset;
        old_offset = offset;
        offset = 0;
        if (-1 == write(temp_buf, size))
        {
            offset = old_offset;
            return (-1);
        }
        offset = old_offset;
    }
    return (0);
}

int PHYSMEM_HANDLE::valid() { return isvalid; }
